package de.hsb.ai.agent.analyzer;

import game.Move;
import game.TestBoard;
import game.Transport;
import jade.core.AID;

import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import weka.classifiers.Classifier;
import weka.classifiers.bayes.NaiveBayes;
import weka.core.Instances;
import de.hsb.ai.domain.DummyAID;
import de.hsb.ai.domain.GameInfo;
import de.hsb.ai.domain.GameStarted;
import de.hsb.ai.domain.ScotlandJadeVocabulary;
import de.hsb.ai.learning.ClassificationWeighterFactory;
import de.hsb.ai.learning.FugitiveAnalysis;
import de.hsb.ai.learning.FugitiveAnalyzer;
import de.hsb.ai.util.ScotlandJadeGameLogger;
import de.hsb.ai.util.ScotlandJadeHelper;
import de.hsb.ai.util.map.ScotlandJadeMapHelper.ScotlandJadeVertex;

/**
 * AnalyzerMasterDetective uses classification to make more
 * educated guesses about Mr. X' behavior.
 * <p>
 * The agent has a maximum of four slaves at hand it can delegate
 * work to, basically tasks like "go to 42".
 */
public class AnalyzerMasterDetective extends AbstractAnalyzerDetective {
	
	public static final String LOCAL_NAME = "analyzer-ma";

	private static final long serialVersionUID = 1L;

	private static final int MAX_SLAVES = ScotlandJadeVocabulary.MAX_PLAYERS - 2;

	private FugitiveMap fugitiveMap;
	
	private List<AnalyzerSlaveDetective> slavePool = new LinkedList<AnalyzerSlaveDetective>();

	private List<AnalyzerSlaveDetective> slaves = new LinkedList<AnalyzerSlaveDetective>();

	private ClassificationWeighterFactory weighterFactory;

	public AnalyzerMasterDetective() {
		super(LOCAL_NAME);		
	}

	@Override
	protected void setup() {
		super.setup();

		for (int i = 1; i <= MAX_SLAVES; ++i) {
			AnalyzerSlaveDetective slave = new AnalyzerSlaveDetective();
			try {
				slavePool.add(slave);
				getContainerController().acceptNewAgent("analyzer-s" + i, slave).start();
			} catch (Exception e) {
				logger.error("registering slave", e);
			}
		}
	}

	@Override
	protected void handleGameStarted(GameStarted g) {
		super.handleGameStarted(g);

		GameInfo gameInfo = getGameInfo();
		
		updateSlaves(gameInfo.getPlayers());

		AID fugitiveAID = gameInfo.getPlayers().get(0);
		String fugitive = DummyAID.isDummy(fugitiveAID) ? null : fugitiveAID.getLocalName();

		Set<ScotlandJadeVertex> possibleFugitivePositions = new HashSet<ScotlandJadeVertex>(); 
		for (int idx : TestBoard.START_POSITIONS) {
			possibleFugitivePositions.add(map.get(idx));
		}
		
		ScotlandJadeGameLogger gameLoger = ScotlandJadeHelper.createGameLogger(gameInfo.getPlayers().size() - 1, fugitive);
		List<List<ScotlandJadeGameLogger.Entry>> entries = gameLoger.read();
		
		weighterFactory = null;
		
		if (!entries.isEmpty()) {
			// Create a Classifier based weighter factory
			weighterFactory = new ClassificationWeighterFactory(
					new FugitiveAnalyzer(map, positions), 
					buildClassifier(FugitiveAnalysis.load(entries.iterator())));
		} else {
			logger.warn("no log entries to analyze from " + gameLoger);
		}
		
		fugitiveMap = new FugitiveMap(map, possibleFugitivePositions, weighterFactory, 0, Transport.NONE);
	}

	
	@Override
	protected Move getMove(List<Move> options) {
		Move nextMove = super.getMove(options);
		if (nextMove == null) {
			nextMove = getMoveTowards(options, lastKnownFugitivePosition);
		}
		if (nextMove == null) {
			logger.warn("LAST RESORT: RANDOM MOVE <----------------------------------------------");
			Collections.shuffle(options);
			nextMove = options.get(0);
		}
		return nextMove;
	}

	@Override
	protected void handleFugitiveMove(ScotlandJadeVertex targetVertex, int transport) {
		super.handleFugitiveMove(targetVertex, transport);
		
		if (targetVertex.getIndex() != 0) {
			// Mr. X has just revealed himself, lets find out where he might go
			fugitiveMap = new FugitiveMap(map, Collections.singleton(targetVertex), weighterFactory, getRound(), transport);
			logger.info("A EXPANDING: " + fugitiveMap.getPaths().size());
			fugitiveMap.setWeightTreshold(0.3);
			// Look ahead two steps
			fugitiveMap.expand(2);
			logger.info("B EXPANDING: " + fugitiveMap.getPaths().size() + " done");
		} else if (transport != Transport.NONE) {
			// Prune paths which are impossible because of the ticket used by Mr. X
			fugitiveMap.prune(getRound() - 1, FugitiveMap.TransportFilter.only(transport));
		}
		
		Iterator<FugitivePath> iter = fugitiveMap.getPaths().iterator();
		for (AnalyzerSlaveDetective slave : slaves) {
			if (!iter.hasNext()) break;
			
			FugitivePath path = iter.next();
			slave.setTarget(path.getEdgeList().get(0).getTargetIndex(path.getStartVertex().getIndex()));
		}
	}

	@Override
	protected void handleDetectiveMove(ScotlandJadeVertex targetVertex, int transport, boolean ownMove) {
		super.handleDetectiveMove(targetVertex, transport, ownMove);
		
		// Reduce possible postions for Mr. X
		if (transport == Transport.NONE) {
			fugitiveMap.removeVertex(targetVertex);
		}
	}

	/**
	 * Creates a {@link Classifier} instance.
	 * @param data the training data-set the created classifier is trained with
	 * @return the trained classifier.
	 */
	private Classifier buildClassifier(Instances data) {
//		Classifier cls = new J48();
		Classifier cls = new NaiveBayes();
		try {
			cls.buildClassifier(data);
		} catch (Exception e) {
			logger.error("building classifier", e);
		}
		return cls;
	}

	/**
	 * Filters all slaves from the pool so we know which ones
	 * are actually participating in this game and are worth
	 * being delegated some work.
	 * 
	 * @param players
	 */
	private void updateSlaves(List<AID> players) {
		for (AbstractAnalyzerDetective slave : slaves) {
			slave.clearPartners();
		}
		
		clearPartners();		
		slaves.clear();
		
		for (AID player : players) {
			for (AnalyzerSlaveDetective slave : slavePool) {
				if (slave.getAID().equals(player)) {
					slaves.add(slave);
					addParnter(slave);
				}
			}
		}
		
		for (AbstractAnalyzerDetective s1 : slaves) {
			for (AbstractAnalyzerDetective s2 : slaves) {
				if (s1 != s2) s1.addParnter(s2);
			}
		}
	}

}
